#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <assert.h>

Widget::Widget(QWidget *parent) :
  QWidget(parent),
  ui(new Ui::Widget),
  once(false),
  kSize(20),
  aStar(kSize, kSize),
  timer(new QTimer(this)),
  kQuit(new QShortcut(this)),
  kReset(new QShortcut(this)),
  kStart(new QShortcut(this))
{
  ui->setupUi(this);
  ui->strategy->addItems(QStringList{"4 directions", "8 directions"});
  ui->strategy->setCurrentIndex(0);
  connect(ui->strategy, static_cast<void(QComboBox::*)(int)>(
            &QComboBox::currentIndexChanged),
          [this](int idx) {
    if (idx == 0) {
      this->aStar.setStrategy(PathFinding::DIRECT_4);
    } else if (idx == 1) {
      this->aStar.setStrategy(PathFinding::DIRECT_8);
    }
    this->on_btnReset_clicked();
  });
  this->init();
}

Widget::~Widget()
{
  timer->stop();
  delete ui;
  for(auto& x: world)
  {
    delete x;
    x = nullptr;
  }
}

void Widget::init()
{
  if(!once)
  {
    this->setFixedSize(width(), height());
    ui->playground->setSpacing(0);
    ui->playground->setMargin(1);
    Square* b = nullptr;
    for(int i = 0; i < kSize; ++i)
    {
      for(int j = 0; j < kSize; ++j)
      {
        b = new Square{i, j};
        world.push_back(b);
        ui->playground->addWidget(b->label(), i, j);
      }
    }
    once = true;
    connect(timer.get(), &QTimer::timeout, this, &Widget::onTimeOut);
    kQuit->setKey(Qt::CTRL + Qt::Key_Q);
    connect(kQuit.get(), &QShortcut::activated, [this] {
      qApp->quit();
    });
    kReset->setKey(Qt::Key_R);
    connect(kReset.get(), &QShortcut::activated, [this] {
      this->on_btnReset_clicked();
    });
    kStart->setKey(Qt::Key_S);
    connect(kStart.get(), &QShortcut::activated, [this] {
      this->on_btnStart_clicked();
    });
  }

  this->setWindowTitle("A* Visualization");
  timer->stop();
  timer->setInterval(50);

  blocks.clear();
  for(auto iter = world.begin(); iter != world.end(); ++iter)
  {
    (*iter)->label()->clear();
    (*iter)->set_id(Square::Id::None).update_style();
  }
  int n = kSize * 4;
  int idx = 0;

  while(n--)
  {
    idx = genRand(world.size()) - 1;
    auto iter = world.begin();
    while(idx-- > 0)
      ++iter;
    (*iter)->set_id(Square::Id::Block).update_style();
    blocks.push_back(*iter);
  }

  this->setUpStartandTarget(Square::Id::Head);
  this->setUpStartandTarget(Square::Id::Target);
}

int Widget::genRand(size_t n)
{
  std::mt19937 gen{rd()};
  std::uniform_int_distribution<> dis(0, n);
  return dis(gen);
}

void Widget::setUpStartandTarget(Square::Id id)
{
  int idx = genRand(world.size()) - 1;
  bool tried = false;
  for(auto iter = world.begin(); iter != world.end(); ++iter)
  {
    auto inner = iter;
    while(idx-- > 0)
      ++inner;
    for(; !tried && inner != world.end(); ++inner)
    {
      if((*inner)->get_id() == Square::Id::None)
      {
        if(id == Square::Id::Target)
        {
          target = *inner;
        }
        else
        {
          start = *inner;
        }
        (*inner)->set_id(id).update_style();
        return;
      }
    }
    tried = true;
    if((*iter)->get_id() == Square::Id::None)
    {
      if(id == Square::Id::Target)
      {
        target = *iter;
      }
      else
      {
        start = *iter;
      }
      (*iter)->set_id(id).update_style();
      return;
    }
  }
}

Square* Widget::findPos(const Point &pos)
{
  for(auto iter = world.begin(); iter != world.end(); ++iter)
  {
    if((*iter)->get_point().equal(pos))
      return *iter;
  }
  return nullptr;
}

void Widget::on_btnReset_clicked()
{
    this->init();
}

void Widget::onTimeOut()
{
  Square* pos = nullptr;
  while(!closeSet.empty())
  {
    pos = findPos(*closeSet.front());
    assert(pos != nullptr);
    if(pos->get_id() != Square::Id::Head &&
       pos->get_id() != Square::Id::Target)
    {
      pos->set_id(Square::Id::Area).update_style(*closeSet.front());
    }
    closeSet.pop_front();
    return;
  }
  while(!path.empty())
  {
    pos = findPos(path.front());
    assert(pos != nullptr);
    if(pos->get_id() != Square::Id::Head &&
       pos->get_id() != Square::Id::Target)
    {
      pos->set_id(Square::Id::Path).update_style(path.front());
    }
    path.pop_front();
    return;
  }
  if(path.empty() && closeSet.empty())
    timer->stop();
}

void Widget::on_btnStart_clicked()
{
  path = aStar.reset(blocks).findBest(start, target);
  closeSet = aStar.closeSet();
  if(path.size() == 0)
  {
    this->setWindowTitle("No Best Path Found");
  }
  else
  {
    this->setWindowTitle(QString("Best Path Take %1 Steps").arg(path.size()));
  }
  timer->start();
}
